Fork me on GitHub

安卓实现耳机口音频转红外发射

###安卓实现耳机口音频转红外发射

前一段时间因为找工作,完了之后又有两个项目做,一个 BLE4.0 的项目,一个红外控制的项目,因此也好久没写文章了。BLE4.0 的资料网上一抓一大把,就不多说了。

虽说红外很早就开始火了,从最早的遥控器,到红外测距等等,但是网上关于 Android 红外开发的相关资料几乎没有。那就只能硬着头皮自己上。

手机自带红外有 ConsumerIrManager 类,很好用,略过。而我们今天看的是另一种红外发送方式:音频转红外


1、相关知识介绍:

这里写图片描述
这是网上找的格力空调的开机短码,将这些数字理解成一个 TA 自己规定的协议,9000,4500 为帧头,560,1690 代表 1,560,560 代表 0。先不管帧头,剩下的翻译过来就是{1,0,1,0 , 1,0,1,0,0,1,0,1,0,1,0,1},再翻译为16进制,即为0xAA,0x55,这是一个开机命令。

注:红外遥控器原理和NEC协议这里面有相关的知识,建议先阅读。


采样率 44100:通俗理解就是 在1s内在一条连续的正玄波上面采集 44100 个点。
载波 38KHZ: 即为我们发出的音频信号需要放在 38KHZ 的载波上才能发送出去被红外接收头接收。

音频转红外要做的就是生成 PCM(单/双声道)数据,即为音频数据,按照硬件支持的 NEC 协议,指定采样率,指定载波,使用 Android SDK 中的 audioTrack 类播放这段音频即可

这里写图片描述

注:多媒体基础知识之PCM数据这里面有 PCM 相关的知识,包括采样率、载波等。

注:硬件自己焊接或者淘宝:android 音频红外发射头,附焊接教程


2、音频调试:

音频调试我使用的电脑软件 cooledit,百度一下就有免费的,再加一根3.5mm 的公对公耳机线。

这里写图片描述这里写图片描述

  • step1:使用遥控精灵搜到你的空调型号。

  • step2:公头线一头插耳机口,一头插电脑音频 mic 口,手机音量调到最大,并在电脑上打开 cooledit 软件。

  • step3:点击 cooledit 的录音键,选择采样率 44100,双声道,16 位。打开遥控精灵,打到你的空调按钮面板,连续点击几次开机键。

  • step4:可以看到 cooledit 软件界面上有一些绿色的声音波形。

这里写图片描述

上面这张图片是我抓的遥控精灵发出的电平信号,可以看到 9ms 的高电平和 4.5ms 的低电平,虽然看到这儿是方波,但是再往后其实 TA 也是正玄波。高电平这儿全部是标准的正玄波组成的,宽度为时间宽度 9ms。

那对于我们来说只要仿造出图中那样的波形,9ms高、4.5ms低………….,即可和遥控精灵一样控制我们的空调了。

3、仿造波形:

要仿造一段 20KHZ(因为耳机口只能输出这么大),采样率 44100 , 16 位双声道的 PCM 音频数据,网上还是有点资料可寻的。

目前,我没有找到输出方波的方法,再加上经过对遥控精灵输出波形的观察,也是输出的正玄波,所以就放心的输出 Sin 正玄波吧。

必要了解 ①:

1
2
3
buffSize = AudioTrack.getMinBufferSize(this.sampleRate,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT) * 4;

AudioTrack:音频播放类。
sampleRate:采样率。
AudioFormat.CHANNEL_OUT_STEREO:双声道输出,即为立体声,但同时也增加了文件大小。
AudioFormat.ENCODING_PCM_16BIT:16位,一个采样点占16位。但同时也增加了文件大小。

必要了解 ②:

1
2
3
4
5
6
7
8
y(t) = A * sin (ωt + φ)
//ω即为角速度,在一段周期内转过了多少角度。
//T为周期。
//f为频率。
ω = 2π/T = 2πf
y(t) = A * sin (2πft + φ)

正玄波重要函数,

  • A: 振幅,这里为1;
  • f : 频率,这里为 freqOfTone;(即为19000HZ)
  • t: 时间,这里为 (i/sampleRate);(当i为44100时是不是就是1s了)
  • φ: 相位,这里为0;

表示下来就是这样:

1
sample[i] = Math.sin(2 * Math.PI * i * (freqOfTone /sampleRate));

必要了解 ③:

1
2
3
4
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
this.sampleRate, AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT, bytes1.length,
AudioTrack.MODE_STREAM);

STREAM_MUSIC:播放类型,有 Alerm、Notification 等。
AudioTrack.MODE_STREAM:MODE 有 STREAM 和 STATIC 两种。STREAM 类型意味着音频可以被连续播放,只需要一直往缓冲池写即可。STATIC 通常用于播放游戏音等,适合短小音频。
bytes1.length:一次可播放音频文件的缓冲池大小。

核心代码片段:

1
2
3
4
5
6
7
8
9
10
11
for (final double dVal : sample) {
final short val = (short) ((dVal * 32767));
final short val_minus = (short) -val;
//左声道
generatedSnd[idx] = (byte) (val & 0x00ff);
generatedSnd[idx+1] = (byte) ((val & 0xff00) >>> 8);
//16位双声道 右声道
generatedSnd[idx+2] = (byte) (val_minus & 0x00ff);
generatedSnd[idx+3] = (byte) ((val_minus & 0xff00) >>> 8);
idx=idx+4;
}

代码解释:

上面第2行和第3行是将振幅缩放到最大振幅(32767是16位整数的最大值)。

上面第5、6行和第8、9行是填充PCM数据,上面有文章讲了PCM数据格式,在16位wav PCM中,低字节到高字节:
|样本大小| 数据格式| 最小值| 最大值|
|: ————-|:————-|: —–|: —–|
|8位PCM | int |-128|127|
| 16位PCM | int | -32768 |32767|

填充完毕后,我们就有了完整的正玄波数据。(即高电平正玄波)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Byte> listByte = new ArrayList<>();
for (int j = 0; j < patterns.length; j++) {
int d=patterns[j];
final int points = (int) ((((double) d / 1000000.0) * sampleRate)*4);
if (j % 2 == 0) {
for (int i = 0; i < points; i++) {
listByte.add(generatedSnd[i]);
}
} else {
for (int i = 0; i < points; i++) {
listByte.add((byte) 0);
}
}
}

代码解释:
patterns 即为我们要发送的电平数组,9000 , 4500… 那个。

看第4行,9000是 μs,9000/1000000,是将 μs 转化为秒,再乘sampleRate 即为在9000μs 这段时间内占有多少个采样点。

因为9000为高电平,4500为低电平,再接下来又为高电平,然后又是低电平….所以偶数位为高电平 ,所以偶数位上这 points 个点都为高电平,到奇数位了这 points 个点都为低电平,低电平使用0表示即可。然后将所有的点拼装到一起,组成完成的PCM数据,使用 AudioTrack 播放即可。

1
2
3
4
5
6
try {
audioTrack.play();
} catch (IllegalStateException e) {
LogUtil.e( e.getMessage());
}
audioTrack.write(listByte, 0, listByte.length);

格力空调的控制码网上一搜一大堆,在这里我不会开放源代码,核心代码已经给出了,自己好好理解理解,分析分析,就可以自己写出来了。

这个也是自己花了好长时间才搞定的,所以请尊重他人劳动成果,不做伸手党。当然有不明白的可以在下面留言,欢迎交流,共同学习。


my QQ : 1003077897
my csdn:http://blog.csdn.net/u012534831
my gay:https://github.com/qht1003077897

欢迎交流。